﻿using System;
using System.Linq;
using OverStore.Configuration.Components;
using OverStore.Configuration.Storage;
using OverStore.Runtime.Storage;
using System.Data;
using System.Collections.Generic;
using OverStore.Sql;
using OverStore.Runtime.ObjectRepository.Attacher;
using OverStore.Runtime.ObjectRepository.CallbackManager;
using OverStore.Runtime.ObjectRepository.Depot;
using OverStore.Configuration.ObjectRepository;
using OverStore.Configuration.ObjectRepository.CallbackManager;
using OverStore.Configuration.ObjectRepository.Attacher;
using OverStore.Runtime.PersistenceServiceProvider;

namespace OverStore.Configuration.Builder.Fluent
{
    /// <summary>
    /// Exposes set of static methods extend fluent builder steps. 
    /// </summary>
    public static class CommonExtensions
    {
        #region Logger

        /// <summary>
        /// Logs to console.
        /// </summary>
        public static OverStoreConfigurationBuilder.DataStorageStep LogToConsole(
            this OverStoreConfigurationBuilder.LoggerStep step)
        {
            return step.WithLogger(Component.Logger.ToConsole());
        }

        /// <summary>
        /// Logs to debug output stream.
        /// </summary>
        public static OverStoreConfigurationBuilder.DataStorageStep LogToDebug(
            this OverStoreConfigurationBuilder.LoggerStep step)
        {
            return step.WithLogger(Component.Logger.Debug());
        }

        /// <summary>
        /// Does not output diagnostics information.
        /// </summary>
        public static OverStoreConfigurationBuilder.DataStorageStep NoLogging(
            this OverStoreConfigurationBuilder.LoggerStep step)
        {
            return step.WithLogger(null);
        }

        #endregion

        #region Data Storage

        /// <summary>
        /// Connects to database using specified ADO.NET provider type. Use this method for connect to SqlServer or SqlServerCe database only,
        /// otherwise use overload with <see cref="SqlFormatting"/> parameter.
        /// </summary>
        public static OverStoreConfigurationBuilder.RepositoryStep ConnectToDatabase<TConnection>(
            this OverStoreConfigurationBuilder.DataStorageStep step,
            string connnectionString,
            ProcessOpenedReadersMode commitWithOpenedReaders)
            where TConnection : IDbConnection
        {
            if (Object.ReferenceEquals(step, null))
            {
                throw new ArgumentNullException("step");
            }

            if (String.IsNullOrEmpty(connnectionString))
                throw new ArgumentNullException("connnectionString");
            var storage = new AdoNetStorageConfiguration<TConnection>
            {
                CommitWithOpenedReadersMode = commitWithOpenedReaders,
                ConnectionString = connnectionString
            };
            return step.WithDataStorage(storage);
        }

        /// <summary>
        /// Connects to database using specified ADO.NET provider type.
        /// </summary>
        public static OverStoreConfigurationBuilder.RepositoryStep ConnectToDatabase<TConnection>(
            this OverStoreConfigurationBuilder.DataStorageStep step,
            string connnectionString,
            ProcessOpenedReadersMode commitWithOpenedReaders,
            SqlFormatting formatting)
            where TConnection : IDbConnection
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(formatting, null))
                throw new ArgumentNullException("formatting");

            if (String.IsNullOrEmpty(connnectionString))
                throw new ArgumentNullException("connnectionString");
            var storage = new AdoNetStorageConfiguration<TConnection>
            {
                CommitWithOpenedReadersMode = commitWithOpenedReaders,
                ConnectionString = connnectionString,
                SqlFormatting = formatting
            };
            return step.WithDataStorage(storage);
        }

        #endregion
    }

    /// <summary>
    /// Exposes set of static methods helps configuring object repository.
    /// </summary>
    public static class ObjectRepositoryExtensions
    {

        /// <summary>
        /// Ends the object repository configuration with no changes tracking, no attaching support and no error transforming.
        /// </summary>
        public static OverStoreConfigurationBuilder.RepositoryStep
            EndObjectRepository<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ChangesTrackerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoChangesTracking().NoErrorTransforming().NoAttachingSupport().EndObjectRepository();
        }

        /// <summary>
        /// Ends the object repository configuration with no attaching support and no error transforming.
        /// </summary>
        public static OverStoreConfigurationBuilder.RepositoryStep
            EndObjectRepository<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ErrorTransformerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoErrorTransforming().NoAttachingSupport().EndObjectRepository();
        }

        /// <summary>
        /// Begins object repository configuration.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.CallbackManagerStep CustomClassRepository<TInstance, TIdentifier>(this OverStoreConfigurationBuilder.RepositoryStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");

            var parameters = new ObjectRepositoryBuilder<TInstance, TIdentifier>.BuilderParameters(step);
            return new ObjectRepositoryBuilder<TInstance, TIdentifier>.CallbackManagerStep(parameters);
        }

        /// <summary>
        /// Sets the type redirection: every time when trying to persist instance of <typeparamref name="TInstance"/> type,
        /// the repository for <typeparamref name="TRedirectToInstance"/> type will be used.
        /// Method works only for object repositories. 
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <typeparam name="TRedirectToInstance">The type actually used for persisting instances of <typeparamref name="TInstance"/> type.</typeparam>
        /// <typeparam name="TRedirectToIdentifier">The type of the identifier of redirected instance.</typeparam>
        public static OverStoreConfigurationBuilder.RepositoryStep SetRedirect<TInstance, TIdentifier, TRedirectToInstance, TRedirectToIdentifier>(
            this OverStoreConfigurationBuilder.RepositoryStep step)
            where TInstance : class
            where TRedirectToInstance : class, TInstance
            where TRedirectToIdentifier : TIdentifier
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.AddRepository(new RedirectObjectRepositoryConfiguration<TInstance, TIdentifier, TRedirectToInstance, TRedirectToIdentifier>());
        }

        /// <summary>
        /// Configures object repository that stores class in table.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        /// <param name="tableName">Name of the table.</param>
        /// <returns></returns>
        public static TableMappingRepositoryStep<TInstance, TIdentifier, OverStoreConfigurationBuilder.RepositoryStep>
            MapClassToTable<TInstance, TIdentifier>(
                this OverStoreConfigurationBuilder.RepositoryStep step, string tableName)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (String.IsNullOrEmpty(tableName))
                throw new ArgumentNullException("tableName");

            var config = new TableMappingRepositoryConfiguration<TInstance, TIdentifier>
            {
                Table = tableName
            };
            return new TableMappingRepositoryStep<TInstance, TIdentifier, OverStoreConfigurationBuilder.RepositoryStep>(
                            config,
                            r => step.AddRepository(r));
        }

        /// <summary>
        /// Uses table schema to build command set.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.ChangesTrackerStep CommandsFromTable<TInstance, TIdentifier>(
            this ObjectRepositoryBuilder<TInstance, TIdentifier>.CommandsStep step,
            bool performDeleteAsUpdate,
            OverStore.Sql.TableSchema.Table table)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(table, null))
                throw new ArgumentNullException("table");
            return step.WithCommands(Component.CommandSet.FromTable(table, performDeleteAsUpdate));
        }

        /// <summary>
        /// Repository does not provide any callbacks.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.DeferredLoaderStep NoCallback<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.CallbackManagerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithCallback(null);
        }

        /// <summary>
        /// Starts description of object hiearachy: if object has references to another object, they must be list in Hierarchy section.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        /// <returns></returns>
        public static
            ObjectStructure<TInstance, ObjectRepositoryBuilder<TInstance, TIdentifier>.DeferredLoaderStep>.ObjectStructureStep
                Hierarchy<TInstance, TIdentifier>(
                    this ObjectRepositoryBuilder<TInstance, TIdentifier>.CallbackManagerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            var param = new ObjectStructure<TInstance, ObjectRepositoryBuilder<TInstance, TIdentifier>.DeferredLoaderStep>.Params(
                callbacks =>
                {
                    if (callbacks == null || callbacks.Count() == 0)
                        return step.NoCallback();
                    else
                        return step.WithCallback(new CompositeCallbackManagerConfiguration<TInstance>
                        {
                            Callbacks = callbacks.ToList()
                        });
                });

            return new ObjectStructure<TInstance, ObjectRepositoryBuilder<TInstance, TIdentifier>.DeferredLoaderStep>.ObjectStructureStep(param);
        }

        /// <summary>
        /// Configures object repository with depot.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        /// <param name="depot">The depot.</param>
        /// <returns></returns>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.MapperStep WithDepot<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.CallbackManagerStep step,
                IComponentConfiguration<IDepot<TInstance, TIdentifier>> depot)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithCallback(null).EagerLoading().WithDepot(depot);
        }


        /// <summary>
        /// Instances must be loaded immediately.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.DepotStep EagerLoading<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.DeferredLoaderStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithDeferredLoader(null);
        }

        /// <summary>
        /// Instances must be loaded immediately.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.DepotStep EagerLoading<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.CallbackManagerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoCallback().WithDeferredLoader(null);
        }

        /// <summary>
        /// Don't track changes.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.ErrorTransformerStep NoChangesTracking<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ChangesTrackerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithChangesTracker(null);
        }

        /// <summary>
        /// Track changes using delegate returns a value represents state.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.ErrorTransformerStep
            TrackChangesUsingDump<TInstance, TIdentifier, TDump>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ChangesTrackerStep step,
                Func<TInstance, TDump> getDump)
            where TInstance : class
            where TDump : IEquatable<TDump>
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(getDump, null))
                throw new ArgumentNullException("getDump");
            return step.WithChangesTracker(Component.InstanceChangesTracker.Delegate<TInstance, TIdentifier, TDump>(getDump));
        }


        /// <summary>
        /// Don't transform an errors.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.AttacherStep
            NoErrorTransforming<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ErrorTransformerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithErrorTransforming(null);
        }

        /// <summary>
        /// Configures the object attacher.
        /// </summary>
        public static AttacherBuilder<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.IdentifierProvider
            ConfigureAttacher<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.AttacherStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            var p = new AttacherBuilder<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.Parameters();
            p.OnCompleted = a => step.WithAttacher(a);
            return new AttacherBuilder<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.IdentifierProvider(p);
        }

        /// <summary>
        /// Configures the object attacher.
        /// </summary>
        public static AttacherBuilder<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.IdentifierProvider
            ConfigureAttacher<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ErrorTransformerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoErrorTransforming().ConfigureAttacher();
        }

        /// <summary>
        /// Configures the object attacher.
        /// </summary>
        public static AttacherBuilder<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.IdentifierProvider
            ConfigureAttacher<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ChangesTrackerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoChangesTracking().NoErrorTransforming().ConfigureAttacher();
        }

        /// <summary>
        /// Configures attaching by identifier: all required attach information is extracted from the instance identifier.
        /// Instance is treated as new if it has default identifier. 
        /// Instance data are always treated as changed.
        /// Identifiers for new instnaces are always generated.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep
            ByIdentifierAttaching<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.AttacherStep step,
                Func<TInstance, TIdentifier> extractIdentifier)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(extractIdentifier, null))
                throw new ArgumentNullException("extractIdentifier");
            return step.WithAttacher(new DelegateAttacherConfiguration<TInstance, TIdentifier>
            {
                ProvideAttachInfo = (instance, provider, attachInfo) =>
                    {
                        var id = extractIdentifier(instance);
                        attachInfo.Identifier = id;
                        attachInfo.IsInstanceNew = EqualityComparer<TIdentifier>.Default.Equals(id, default(TIdentifier));
                        attachInfo.AreInstanceDataChanged = true;
                        attachInfo.GenerateNewIdentifier = true;
                    }
            });
        }

        /// <summary>
        /// Configures attaching by identifier: all required attach information is extracted from the instance identifier.
        /// Instance is treated as new if it has default identifier. 
        /// Instance data are always treated as changed.
        /// Identifiers for new instnaces are always generated.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep
            ByIdentifierAttaching<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.AttacherStep step,
                Func<TInstance, IPersistenceServiceProvider, TIdentifier> extractIdentifier)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(extractIdentifier, null))
                throw new ArgumentNullException("extractIdentifier");
            return step.WithAttacher(new DelegateAttacherConfiguration<TInstance, TIdentifier>
            {
                ProvideAttachInfo = (instance, provider, attachInfo) =>
                {
                    var id = extractIdentifier(instance, provider);
                    attachInfo.Identifier = id;
                    attachInfo.IsInstanceNew = EqualityComparer<TIdentifier>.Default.Equals(id, default(TIdentifier));
                    attachInfo.GenerateNewIdentifier = true;
                    attachInfo.AreInstanceDataChanged = true;
                }
            });
        }

        /// <summary>
        /// Configures attaching by identifier: all required attach information is extracted from the instance identifier.
        /// Instance is treated as new if it has default identifier. 
        /// Instance data are always treated as changed.
        /// Identifiers for new instnaces are always generated.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep
            ByIdentifierAttaching<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ErrorTransformerStep step,
                Func<TInstance, TIdentifier> extractIdentifier)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(extractIdentifier, null))
                throw new ArgumentNullException("extractIdentifier");
            return step.NoErrorTransforming().ByIdentifierAttaching(extractIdentifier);
        }

        /// <summary>
        /// Configures attaching by identifier: all required attach information is extracted from the instance identifier.
        /// Instance is treated as new if it has default identifier. 
        /// Instance data are always treated as changed.
        /// Identifiers for new instnaces are always generated.
        /// 
        /// Repository is configures for don't use changes tracking and error transforming.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep
            ByIdentifierAttaching<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.ChangesTrackerStep step,
                Func<TInstance, TIdentifier> extractIdentifier)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(extractIdentifier, null))
                throw new ArgumentNullException("extractIdentifier");
            return step.NoChangesTracking().NoErrorTransforming().ByIdentifierAttaching(extractIdentifier);
        }

        /// <summary>
        /// Configures object repository as not supported attaching, for example, for instances that does not have identifier as part of their data.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep
            NoAttachingSupport<TInstance, TIdentifier>(this ObjectRepositoryBuilder<TInstance, TIdentifier>.AttacherStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithAttacher(null);
        }

        /// <summary>
        /// Configures object repository as not supported attaching, for example, for instances that does not have identifier as part of their data.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep
            NoAttachingSupport<TInstance, TIdentifier>(this ObjectRepositoryBuilder<TInstance, TIdentifier>.ErrorTransformerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoErrorTransforming().WithAttacher(null);
        }

        /// <summary>
        /// Configures object repository as not supported attaching, for example, for instances that does not have identifier as part of their data.
        /// </summary>
        public static ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep
            NoAttachingSupport<TInstance, TIdentifier>(this ObjectRepositoryBuilder<TInstance, TIdentifier>.ChangesTrackerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoChangesTracking().NoErrorTransforming().WithAttacher(null);
        }
    }

    /// <summary>
    /// Exposes set of static methods helps to configure object depot.
    /// </summary>
    public static class DepotExtensions
    {
        /// <summary>
        /// Begins configuration of the delegate depot.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        /// <returns></returns>
        public static DepotBuilder<TInstance, TIdentifier>.DepotCreateInstanceStep
            DelegateDepot<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.DepotStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return new DepotBuilder<TInstance, TIdentifier>.DepotCreateInstanceStep(
                new OverStore.Configuration.ObjectRepository.Depot.DelegateDepotConfiguration<TInstance, TIdentifier>(),
                step);
        }

        /// <summary>
        /// Begins configuration of the delegate depot.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        /// <returns></returns>
        public static DepotBuilder<TInstance, TIdentifier>.DepotCreateInstanceStep
            DelegateDepot<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.CallbackManagerStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.NoCallback().EagerLoading().DelegateDepot();
        }

        /// <summary>
        /// Identifier generator creates new guids.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <param name="step">The step.</param>
        /// <returns></returns>
        public static DepotBuilder<TInstance, Guid>.EndStep Guid<TInstance>(
                this DepotBuilder<TInstance, System.Guid>.DepotIdentifierGeneratorStep step)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithIdentifierGenerator(Component.IdentifierGenerator.Guid<TInstance>());
        }

        /// <summary>
        /// Generated identifiers sequentially using initial value and increment. 
        /// The identifier and increment are converted to <see cref="System.Int32"/> type before calculating next identifier.
        /// </summary>
        public static DepotBuilder<TInstance, TIdentifier>.EndStep Incremental<TInstance, TIdentifier>(
                this DepotBuilder<TInstance, TIdentifier>.DepotIdentifierGeneratorStep step,
                TIdentifier initialValue,
                TIdentifier increment)
            where TInstance : class
            where TIdentifier : struct
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithIdentifierGenerator(
                Component.IdentifierGenerator.Delegate<TInstance, TIdentifier>(
                    (entity, lastIdentifier) =>
                    {
                        int converted = Convert.ToInt32(lastIdentifier);
                        int convertedIncr = Convert.ToInt32(increment);
                        var next = converted + convertedIncr;
                        return (TIdentifier)Convert.ChangeType(next, typeof(TIdentifier), System.Globalization.CultureInfo.CurrentCulture);
                    },
                    initialValue));
        }
    }

    /// <summary>
    /// Exposes set of static methods allows to create mapper.
    /// </summary>
    public static class MapperExtensions
    {

        /// <summary>
        /// Configures delegate mapper.
        /// </summary>
        public static DelegateMapperGetObjectDataStep<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.CommandsStep>
            MapUsingDelegate<TInstance, TIdentifier>(
                this ObjectRepositoryBuilder<TInstance, TIdentifier>.MapperStep step)
            where TInstance : class
        {
            return new DelegateMapperGetObjectDataStep<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.CommandsStep>(
                new OverStore.Configuration.ObjectRepository.Mapping.DelegateMapperConfiguration<TInstance>(),
                m => step.WithMapper(m));
        }
    }

    /// <summary>
    /// Exposes set of static methods helps to create configuration for ObjectReader.
    /// </summary>
    public static class ObjectReaderExtensions
    {
        #region Identifier Provider

        /// <summary>
        /// Sets delegate identifier provider to default read variants.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        /// <param name="getIdentifier">The get identifier.</param>
        /// <returns></returns>
        public static ParameterlessReaderBuilder<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.EndStep
            DelegateIdentifierProvider<TInstance, TIdentifier>(
                this ParameterlessReaderBuilder<TInstance, TIdentifier, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.SetIdentifierProviderStep step,
                Func<TypedRecord, TIdentifier> getIdentifier)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(getIdentifier, null))
                throw new ArgumentNullException("getIdentifier");
            return step.WithIdentifierProvider(Component.IdentifierProvider.Delegate(getIdentifier));
        }

        /// <summary>
        /// Sets delegate identifier provider to default read variants.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <param name="step">The step.</param>
        /// <param name="getIdentifier">The get identifier.</param>
        /// <returns></returns>
        public static ParameterlessReaderBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, OverStoreConfigurationBuilder.RepositoryStep>>.EndStep
            DelegateIdentifierProvider<TInstance, TIdentifier>(
                this ParameterlessReaderBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, OverStoreConfigurationBuilder.RepositoryStep>>.SetIdentifierProviderStep step,
                Func<TypedRecord, TIdentifier> getIdentifier)
            where TInstance : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(getIdentifier, null))
                throw new ArgumentNullException("getIdentifier");
            return step.WithIdentifierProvider(Component.IdentifierProvider.Delegate(getIdentifier));
        }

        /// <summary>
        /// Sets delegate identifier provider to parameterized read variants.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <typeparam name="TParameter">The type of the parameter.</typeparam>
        /// <param name="step">The step.</param>
        /// <param name="getIdentifier">The get identifier.</param>
        /// <returns></returns>
        public static ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.EndStep
            DelegateIdentifierProvider<TInstance, TIdentifier, TParameter>(
                this ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.SetIdentifierProviderStep step,
                Func<TypedRecord, TIdentifier> getIdentifier)
            where TInstance : class
            where TParameter : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(getIdentifier, null))
                throw new ArgumentNullException("getIdentifier");
            return step.WithIdentifierProvider(Component.IdentifierProvider.Delegate(getIdentifier));
        }

        /// <summary>
        /// Sets delegate identifier provider to parameterized read variants.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <typeparam name="TParameter">The type of the parameter.</typeparam>
        /// <param name="step">The step.</param>
        /// <param name="getIdentifier">The get identifier.</param>
        /// <returns></returns>
        public static ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, TableMappingRepositoryStep<TInstance, TIdentifier, OverStoreConfigurationBuilder.RepositoryStep>>.EndStep
            DelegateIdentifierProvider<TInstance, TIdentifier, TParameter>(
                this ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, TableMappingRepositoryStep<TInstance, TIdentifier, OverStoreConfigurationBuilder.RepositoryStep>>.SetIdentifierProviderStep step,
                Func<TypedRecord, TIdentifier> getIdentifier)
            where TInstance : class
            where TParameter : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            if (Object.ReferenceEquals(getIdentifier, null))
                throw new ArgumentNullException("getIdentifier");
            return step.WithIdentifierProvider(Component.IdentifierProvider.Delegate(getIdentifier));
        }

        #endregion

        /// <summary>
        /// Maps the parameters using reflection.
        /// </summary>
        /// <typeparam name="TInstance">The type of the instance.</typeparam>
        /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
        /// <typeparam name="TParameter">The type of the parameter.</typeparam>
        /// <param name="step">The step.</param>
        /// <returns></returns>
        public static ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.SetIdentifierProviderStep
            MapParametersUsingReflection<TInstance, TIdentifier, TParameter>(
                this ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, ObjectRepositoryBuilder<TInstance, TIdentifier>.EndRepositoryStep>.SetMapperStep step)
            where TInstance : class
            where TParameter : class
        {
            if (Object.ReferenceEquals(step, null))
                throw new ArgumentNullException("step");
            return step.WithParametersMapper(Component.Mapper.Reflection<TParameter>());
        }

    }
}
